/**
 * Performance API 是多个子级 API 共同构成的，相当庞大，但并不复杂。
 */
/**
 *    High Resolution Time 标准定义了一个 Perfomance 接口，用于在客户端应用内测量时延。
 *    Performance 接口之所以说是高精度的，是因为它的测量精度达到了千分之5毫秒（5微秒）。该接口有很多使用场景，
 * 比如：测量帧速率（用于动画）、作为时间标记等。现在的系统平台的时钟总是会因各种原因而发生偏移，为此，
 * Perfomance 接口定义了一个单调递增的时钟模型，和一个特殊的 DOMHighResTimeStamp 时间类型，而不是使用 Date.now() 接口。
 */

/**
 *    DOMHighResTimeStamp 类型是 double 实例用于存储是时间值。这个时间值可以是一个离散的时间点，也可以是两个离散时间点的差值。
 * 单位是毫秒，且可精确到5微秒，也可能因软硬件显示而不能提供高精度时间。
 *    DOMHighResTimeStamp 类型没有属性，它是一个实型精度的浮点数。描述两个离散时间点之间的时间戳差值。
 * 它开始计算的时间点，可以是代码指定的或者是时间原点（time origin）
 * Time Origin
 *    时间原点是计算当前文档使用期时开始时间的标准时间，它的计算如下：
 *      1.如果全局对象四 Window 对象，tiem origin 按如下方法计算：
 *          a) 如果当前文档是 Window 对象下第一个加载完成的文档，那么 time origin 就是当前浏览器上下文被创建时的时间
 *          b) 如果在前一个文档卸载时弹出了确认弹窗，那么下一个页面的 time origin 就是用户确认导航的时间戳
 *          c) 如果上面两者都不满足，那么 time origin 就是导航结果页面 window 对象创建 document 对象时的时间戳
 *      2.如果全局对象是 WorkerGlobalScope ，那么 time origin 是该工作进程被创建时的时间戳
 *      3.所欲其他情形下（nodejs 等），time origin 为 undefined
 */

/**
 * 方法：
 *    Performance 接口有两个方法：
 *        1. now()， 返回一个 DOMHighResTimeStamp 值，其值受导航开始时间和作用域影响。如果作用域与 window 则返回当前浏览器上下文创建时间，如果作用域是 worker 那么返回当前进程创建的时间戳。
 *        2. toJSON()，返回一个携带所有可序列化属性的 Performance 对象。
 * 属性：
 *    Performance 接口有两个属性：
 *        1. timing：返回一个 PerformanceTiming 对象 ，包含延迟相关信息，比如导航开始的时间、重定向和响应的开始结束时间
 *        2. navigation：返回一个 PerformanceNavigation 对象，表示给定浏览器上下文中导航的类型
 */

/**
 * PerformanceTiming 在规格标准中已经被 PerformanceNavigationTiming 取代。
 * 关于 PerformanceNavigationTiming 接口和PerformanceNavigation 接口 可以查看本项目中 navigationTiming 一节。
 * PerformanceTiming 接口和 PerformanceNavigation 接口依然存在，但不再与 Performance 直接组合，而是作为 PerformanceNavigationTiming 向前兼容的做法。
 */

/**
 *    PerformanceEntry 接口封装了作为性能时间线一部分的一个单一性能指标。
 * 调用 performance 对象的 mark 或者 measure 方法会立即创建一个性能实体对象。 性能实体对象也可以通过加载一个资源文件来间接创建。
 *    PerformanceEntry 接口本身没有方法和属性（PerformanceNavigationTiming有扩展），是一个抽象类。总是下面这些子类型其中之一：
 *        PerformanceMark
 *        PerformanceMeasure
 *        PerformanceFrameTiming
 *        PerformanceNavigationTiming
 *        PerformanceResourceTiming
 *        PerformancePaintTiming
 * PerformanceEntry 接口实例 {
 *    name: 只读，返回值取决于 PerformanceEntry 对象的子类型。
 *    entryType: {
 *        frame,  navigation, resource, mark,  measure, paint
 *    } 枚举，分别对应六种子类型
 *    startTime: 整个性能标准开始的时间戳
 *    duration: 性能事件持续的时长
 * }
 */
// 下面的示例检查了当前浏览器支持的实体类型，并打打印他们的值
function print_PerformanceEntries () {
  // 通过 getEntries() 获取性能实体列表
  var p = performance.getEntries();
  for(var i = 0; i < p.length; i += 1) {
    console.log(`PerformanceEntry[${i}]`);
    print_PerformanceEntry(p[i])
  }
}

function print_PerformanceEntry(perfEntry) {
  var properties = ['name', 'entryType', 'startTime', 'duration'];
  for(var i = 0; i < properties.length; i += 1) {
    var supported = properties[i] in perfEntry;
    if(supported) {
      var value = perfEntry[properties[i]]
      console.log(`...${properties[i]} = ${value}`)
    } else {
      console.log(`...${properties[i]} is NOT supproted`);
    }
  }
}
print_PerformanceEntries();

/**
 *    PerformanceFrameTiming 接口是一个抽象接口，提供了关于浏览器事件循环的帧时序数据。
 * 一个 frame 代表了一次事件循环中浏览器所做的所有工作，比如处理DOM事件，CSS动画等等。
 * 当帧速率为 60fps 时，在 60Hz 的刷新率下会给用户带来良好的体验，这意味着浏览器处理一个帧的耗时约为 16.7ms
 *    一个应用中可以为 “frame” 类型的性能实体注册一个 PerformanceObserver，这个观察者可以检索每个帧事件的耗时。
 * 这些信息有助于识别需要花费很长事件才能提供优秀用户体验的领域。
 */

/**
 * PerformanceObserver 接口用于检测新能测量事件，并且当浏览器新能时序中记录新的性能实体时会收到通知。
 * PerformanceObserver 接口 {
 *    构造函数：{Function (function)}
 *    observe: {Function ({entryTypes: Array[string]})} 指定要监测的实体类型集合。当指定类型的性能实体被记录时会调用观察者的回调函数
 *    disconnect: {Function} 停止性能观察者回调
 *    takeRecords: {Function} 返回当前保存在观察者中的性能实体列表，并清空它。
 * }
 */
// 下面的示例简单展示了观察者的用法
var observer = new PerformanceObserver(function (list, obj) {
  console.log(list, obj);
  var entries = list.getEntries();
  for(var i = 0; i < entries.length; i += 1) {
    console.log(`${entries[i].name}: ${entries[i].entryType}`);
  }
});
observer.observe({ entryTypes: ['mark']});

function perf_observer(list, observe) {
  console.log('disconnect');
  observer.disconnect();
}
var observer2 = new PerformanceObserver(perf_observer);
observer2.observe({ entryTypes: ['resource'] });

var records = observer2.takeRecords();
console.log(records);

/**
 *    PerformanceMark 是PerformanceEntry 对象的一个 entryType 为“mark”的抽象接口。
 * 这个实体类型的实例均由 performance.mark() 函数调用创建，添加一个 DOMHighResTimeStramp 到浏览器的性能时序中去。
 */

/**
 *    PerformanceMeasure 是PerformanceEntry 对象的一个 entryType 为“measure”的抽象接口。
 * 这个实体类型的实例均由 performance.measure() 函数调用创建，
 */

/**
 *    PerformanceResourceTiming 接口提供了方法和属性用于检索和分析关于加载一个应用资源时的网络时间数据。
 * 这个接口的属性为网络事件使用高精度时间值创建了资源加载时序。这些网络事件包括重定向的开始和结束、fetch 的开始、DNS查询的开始和结束、响应的开始和结束、等等。
 * 此外这个接口还扩展了 PerformanceEntry 接口，增加了几个属性用以提供获取的资源的大小。
 * 
 * PerformanceResourceTiming 接口实例 {
 *    ... 继承自 PerformanceNavigationTiming 的四个属性
 *    initialtorType: {string} 当前性能实体初始化的资源类型
 *    nextHopProtocal: {string} 表示获取资源的网络协议
 *    workerStart: {DOMHighResTimeStamp} 如果一个服务工作线程已经在运行，则该属性立即返回 FetchEvent 执行前的时间点。如果服务工作线程还未运行，则立即返回开始运行前的时间点。如果资源文件没有被服务进程截取则返回0
 *    redirectStart: {DOMHighResTimeStamp} 表示初始化重定向的请求的开始时间
 *    redirectEnd: {DOMHighResTimeStamp} 表示接收最后的重定向的返回数据完成时刻的时间
 *    fetchStart:{DOMHighResTimeStamp} 表示浏览器开始获取资源前的时间点
 *    domainLookupStart: {DOMHighResTimeStamp} 表示浏览器开始对资源路径进行解析前的时间点
 *    domainLookupEnd: {DOMHighResTimeStamp} 表示浏览器完成资源路径解析时的时间点
 *    connectStart；{DOMHighResTimeStamp} 表示浏览器与服务端建立查询连接前的时间点
 *    connectEnd: {DOMHighResTimeStamp} 表示浏览器与服务端建立查询连接完成时的时间点
 *    secureConnectionStart: {DOMHighResTimeStamp} 表示浏览器与当前连接开始握手处理前的时间点
 *    requestStart: {DOMHighResTimeStamp} 表示浏览器开始想服务端请求资源前的时间点
 *    responseStart: {DOMHighResTimeStamp} 表示浏览器获取服务端响应的第一个字节的时间点
 *    responseEnd: {DOMHighResTimeStamp} 表示浏览器获取服务端响应的最后一个字节的时间点，或者是传输连接关闭前的时间点，这二者之一
 *    transferSize: {number} 获取资源的大小，包括响应表头的大小和有效负载体的大小
 *    encodedBodySize: {number} 表示在删除任意的连接编码前，获取到的数据体的总大小。
 *    decodedBodySize: {number} 表示在删除任意的连接编码后，获取到的数据体的总大小。
 *    serverTiming: {Array[PerformanceServerTiming]} 与服务端连接的时间测量结果
 * }
 */
// 下面的几个示例展示了如何使用资源时序数据
/**
 * 这个示例计算了下面几个阶段的时间开销
 *    重定向（redirectStart 和 redirectEnd)
 *    DNS解析（domainLookupStart 和 domainLookupEnd）
 *    TCP握手（connectStart 和 connectEnd）
 *    响应（responseStart 和 responseEnd）
 *    从资源获取开始到响应结束（fetchStart||requestStart 到 responseEnd）
 */
function calculate_load_time () {
  // 检查 performance 是否被支持
  if(performance === undefined) {
    console.log('= Calulate Load Times: performance NOT supported');
    return;
  }

  // 获取 “resource” 性能实体列表
  var resources = performance.getEntriesByType('resource');
  if(resources === undefined || resources.length <= 0) {
    console.log(`= Calculate Load Times: there are NO \`resource\` performance records`);
    return;
  }

  console.log('= Calculate Load Times');
  for(var i = 0; i < resources.length; i += 1) {
    console.log(`== Resource["${i}"] - ${resources[i].name}`);
    // 重定向时间
    var t = resources[i].redirectEnd - resources[i].redirectStart;
    console.log(`...Redirect time = ${t}`);

    // DNS解析时间
    t = resources[i].domainLookupEnd - resources[i].domainLookupStart;
    console.log(`...DNS lookup time = ${t}`);

    // TCP 握手时间
    t = resources[i].connectEnd - resources[i].connectStart;
    console.log(`...TCP time = ${t}`);

    // 安全连接时间
    t = (resources[i].secureConnectionStart > 0) ? (resources[i].connectEnd - resources[i].secureConnectionStart) : '0';
    console.log(`...Secure connection time = ${t}`);

    // 响应时间
    t = resources[i].responseEnd - resources[i].responseStart;
    console.log(`...Response time = ${t}`)

    // 获取开始直到响应结束
    t = (resources[i].fetchStart > 0) ? (resources[i].responseEnd - resources[i].fetchStart) : '0';
    console.log(`...Fetch until response end time = ${t}`)

    // 请求开始直到响应结束
    t = (resources[i].requestStart > 0) ? (resources[i].responseEnd - resources[i].requestStart) : '0';
    console.log(`...Request until response end time = ${t}`)

    // 开始直到请求结束
    t = (resources[i].startTime > 0) ? (resources[i].responseEnd - resources[i].startTime) : '0';
    console.log(`...Start until response end time = ${t}`);
  }
}
calculate_load_time();

/**
 *    在进行性能测量时，传输数据本身的大小也是重要因素，PerformanceResourceTiming 接口有三个参数用于
 * 记录数据大小。下面的示例展示了这三个参数的使用
 */
function display_size_data () {
  // 检查当前上下文是否支持这几个字段
  if(performance === undefined) {
    console.log(`= Display Size Data: performance NOT supported`);
    return;
  }

  var list = performance.getEntriesByType('resource');
  if(list === undefined) {
    console.log(`= Display Size Data: performance.getEntriesByType is NOT supported`)
    return;
  }

  console.log(`= Display Size Data`);
  for(var i = 0; i < list.length; i += 1) {
    console.log(`== Resource[${i}] = ${list[i].name}`);
    if("decodedBodySize" in list[i])
      console.log(`...decodedBodySize[${i}] = ${list[i].decodedBodySize}`);
    else
      console.log(`...decodedBodySize[${i}] = NOT supported`);

    if("encodedBodySize" in list[i]) 
      console.log(`...encodedBodySize[${i}] = ${list[i].encodedBodySize}`);
    else 
      console.log(`...encodedBodySize[${i}] = NOT supported`);

    if("transferSize" in list[i])
      console.log(`...transferSize[${i}] = ${list[i].transferSize}`);
    else
      console.log(`...transferSize[${i}] = NOT supported`);
  }
}
display_size_data();

/**
 *    尽管浏览器被要求在资源时序缓冲中支持最少150个资源时间性能实体，但有些应用使用的资源数超过了这一限制。
 * 为了帮助开发者管理缓冲尺寸，PerformanceResourceTiming 接口在 Performance 接口中扩展了两个方法：
 *    clearResourceTimings: {Function} 清空当前浏览器资源性能实体缓冲区
 *    setResourceTimingBufferSize: {Function(number)} 设置资源性能实体缓冲区允许缓冲实体个数
 * 下面的示例展示了这个两个方法的使用
 */
function clear_resource_timings () {
  if(performance === undefined) {
    console.log(`= Display Size Data: performance NOT supported`);
    return;
  }

  // 检查 performance.clearResourceTiming 是否支持
  console.log(`= Print performance.clearResourceTimings`);
  var supported = typeof performance.clearResourceTimings === 'function';
  if(supported) {
    console.log("...performance.clearResourceTimings = supported");
    performance.clearResourceTimings();
  } else {
    console.log('...performance.clearResourceTiming = NOT spported'):
    return;
  }

  // 获取实体列表，应返回0
  var p = performance.getEntriesByType('resource');
  if(p.length === 0)
    console.log('...Performance data buffer cleared');
  else
    console.log('...Performance data buffer NOT cleared');
}

function set_resource_timing_size (n) {
  if(performance === undefined) {
    console.log(`= Display Size Data: performance NOT supported`);
    return;
  }

  // 检查 setResourceTimingBufferSize 是否支持
  console.log(`= performance.setResourceTimingBufferSize`);
  var supported = typeof performance.setResourceTimingBufferSize === 'function';
  if (supported) {
    console.log("... Performance.setResourceTimingBufferSize() = supported");
    performance.setResourceTimingBufferSize(n);
  } else {
    console.log("... Performance.setResourceTimingBufferSize() = NOT supported");
  }
}
clear_resource_timings();

/**
 *    除了上述的方法属性外，Performance 接口还有一个 onresourcetimingbufferfull 事件，
 * 当浏览器的资源性能实体缓冲慢时会触发回调。下面的示例展示了如何处理
 */
function buffer_full (event) {
  console.log(`WARNING: Resource Timing Buffer is FULL!`);
  set_resource_timing_size(200);
}

function init () {
  // 加载一些文件，以触发 Fetch 事件
  var img1 = new Image();
  img1.src = 'https://developer.mozilla.org/static/img/opengraph-logo.png';
  var img2 = new Image();
  img2.src = 'http://mozorg.cdn.mozilla.net/media/img/firefox/firefox-256.e2c1fc556816.jpg';
  // 设置事件监听
  performance.onresourcetimingbufferfull = buffer_full;
}
init();